#include "RuneDetect.h"
#include <iostream>
#include <vector>
#include<math.h>
using namespace std;
using namespace cv;

RuneDetect::RuneDetect()
{
    _is_recorded = false;
    _color = BLUE;
    _is_continuous = true;
}

RuneDetect::RuneDetect(bool is_recorded)
{
    _is_recorded = is_recorded;
    _color = BLUE;
    _is_continuous = true;
}

RuneDetect::RuneDetect(bool is_recorded, int color, bool is_continuous)
{
    _is_recorded = is_recorded;
    _color = color;
    _is_continuous = is_continuous;
}

RuneDetect::~RuneDetect()
{
}

void RuneDetect::target_predict(cv::Mat &src, cv::Point2f target, cv::Point2f center)
{
    Mat rot_mat = getRotationMatrix2D(center, -30, 1.1);
    Mat target_xy = (Mat_<float>(3,1)<< target.x,target.y,1.0);
    rot_mat.convertTo(rot_mat,CV_32F);
    target_xy.convertTo(target_xy,CV_32F);
    Mat resPoint = rot_mat*target_xy;
    circle(src, Point2f(resPoint.at<float>(0,0), resPoint.at<float>(1,0)), 15, Scalar(0,255,0), 2, 8, 0);
    // putText(src, "predict_point", Point2f(resPoint.at<float>(0,0), resPoint.at<float>(1,0)), FONT_HERSHEY_SIMPLEX, 0.4, Scalar(255, 255, 255), 1, 8, 0);
}

int RuneDetect::ImageProcess(string video_path)
{
    VideoCapture capture(video_path);
    if (!capture.isOpened())
    {
        cout << "video can not be opened!";
        return -1;
    }
    namedWindow("效果视频", WINDOW_GUI_EXPANDED);

    /**
     * @brief 写入视频的参数设置
     * 
     */
    if (_is_recorded) 
    {
        double fps = capture.get(CAP_PROP_FPS);
        Size size(
            capture.get(CAP_PROP_FRAME_WIDTH),
            capture.get(CAP_PROP_FRAME_HEIGHT));
        writer.open("./效果保存视频.avi", VideoWriter::fourcc('M', 'P', 'G', '2'), fps, size);
    }

    Mat src, gray_img, binBright_img;
    vector<Mat> channels;

    while (capture.read(src))
    {
        split(src, channels);
        if (_color) //判断是否为红光，是则执行以下代码块，不是则执行else的代码块
        {
            gray_img = channels.at(2) - channels.at(0); //增强红色通道
        }
        else
        {
            gray_img = channels.at(0) - channels.at(2); //增强蓝色通道
        }
        threshold(gray_img, binBright_img, 70, 255, THRESH_BINARY); //二值化图像
        // imshow("二值化" ,binBright_img);

        /**
         * @brief 形态学操作，连接目标板箭头的同时去除小黑洞
         * 
         */
        Mat element = getStructuringElement(MORPH_RECT, Size(5, 5));
        morphologyEx(binBright_img, binBright_img, MORPH_CLOSE, element, Point(-1, -1)); //进行闭操作使得目标板下得箭头能相连
        // imshow("二值化图", binBright_img);                                               //查看视频帧二值化效果

        /**
         * @brief 轮廓及其像素点坐标的寻找
         * 
         */
        vector<vector<Point>> contours; //存储轮廓像素点的二维数组
        vector<Vec4i> hierarchy;        //存储父轮廓及内嵌轮廓等上级、下级、同级信息的数组
        findContours(binBright_img, contours, hierarchy,
                     RETR_TREE, CHAIN_APPROX_NONE, Point(0, 0)); //以相距不超过1像素的距离寻找轮廓，并存储轮廓像素点坐标

        /**
         * @brief 记录某个轮廓含有的子轮廓的数目
         * 
         */
        int contour[25] = {0};                       //存储某个父轮廓含有子轮廓数的数组
        for (size_t i = 0; i < contours.size(); i++) //这里的i为所有轮廓（包括外层及内嵌轮廓）
        {
            if (hierarchy[i][3] != -1) //如果父轮廓的索引编号不为-1，即该轮廓为父轮廓，含有内嵌轮廓
            {
                contour[hierarchy[i][3]]++; //某个轮廓上一层父轮廓索引号的位置自增，即contour[i]表示某个轮廓含有的子轮廓的数目
            }
        }

        /**
         * @brief 由于大轮一般只出现一个目标板（目标板内嵌轮廓数为1）
        因此当识别出有多个内嵌轮廓数为1的父轮廓时，跳出此次循环进行下一视频帧的处理
         * 
         */
        int cnt = 0;
        for (size_t j = 0; j < contours.size(); j++)
        {
            if (contour[j] == 1)
            {
                cnt++; //若遍历到一个含有一个内嵌轮廓的父轮廓，则cnt自增1
            }
        }

        /**
         * @brief 判断外接矩形的形状并标定轮廓外接矩形
         * 
         */
        for (size_t k = 0; k < contours.size(); k++) //遍历所有轮廓的索引号
        {
            if (cnt > 1)
                break;

            if (contour[k] == 1) //判断当前轮廓含有的子轮廓数目是否为1
            {
                int num = hierarchy[k][2];                      //某个只含有一个内嵌轮廓的父轮廓的下一层子轮廓的索引编号
                RotatedRect rrect = minAreaRect(contours[num]); //返回该内嵌轮廓的外接矩形
                Point2f point[4];                               //存储外接矩形的四个顶点的数组
                rrect.points(point);                            //将外接矩形的四个顶点映射到数组中
                /*剔除含有三个内嵌轮廓的非目标板的外接矩形（长短边长之比大于2.5）的影响*/
                if (rrect.size.height > rrect.size.width && ((rrect.size.height / rrect.size.width) > 2.5))
                {
                    continue; //进行下一子轮廓为1的父轮廓外接矩形的判断与绘制
                }
                else if (rrect.size.height < rrect.size.width && ((rrect.size.width / rrect.size.height) > 2.5))
                {
                    continue;
                }
                /*画出轮廓外接矩形*/
                for (size_t i = 0; i < 4; i++)
                {
                    line(src, point[i], point[(i + 1) % 4], Scalar(0, 0, 255), 2, 8, 0);
                }
                Point2f target = (point[0] + point[2]) / 2;
                circle(src, target, 2, Scalar(255, 255, 0), -1, 8, 0);
                putText(src, "target", target, FONT_HERSHEY_SIMPLEX, 0.4, Scalar(255, 255, 255), 1, 8, 0);

                /* 目标板出现时画出中心R的矩形轮廓 */
                int cnt2 = 0;
                RotatedRect centerR;
                for (size_t j = 0; j < contours.size(); j++) //再次遍历所有轮廓
                {
                    if (cnt2 == 1) //当筛选出多个符合条件的轮廓时，跳出循环
                        break;

                    if (hierarchy[j][2] == -1 && hierarchy[j][3] == -1) //如果没有下一层子轮廓或父轮廓
                    {
                        centerR = minAreaRect(contours[j]);
                        /* 剔除长短边比例过小或过大，及长边过长的轮廓 */
                        if (centerR.size.width > centerR.size.height && (centerR.size.width / centerR.size.height) > 1.1 && (centerR.size.height / centerR.size.width) < 0.9 && centerR.size.width > 15)
                        {
                            continue;
                        }
                        if (centerR.size.width < centerR.size.height && (centerR.size.height / centerR.size.width) > 1.1 && (centerR.size.width / centerR.size.height) < 0.9 && centerR.size.height > 15)
                        {
                            continue;
                        }
                        if (centerR.size.width > centerR.size.height && centerR.size.height < 5)
                        {
                            continue;
                        }
                        if (centerR.size.width < centerR.size.height && centerR.size.width < 5)
                        {
                            continue;
                        }
                        if (centerR.size.width * centerR.size.height > 500 || centerR.size.width * centerR.size.height < 90) //设定centerR筛选面积
                        {
                            continue;
                        }
                        // cout << "centerR面积：" << centerR.size.width*centerR.size.height << "\tcenterR.width:" << centerR.size.width
                        // << "\tcenterR.height:" << centerR.size.height << endl;

                        cnt2++;
                        Point2f Rpoint[4];
                        centerR.points(Rpoint); //将轮廓外接矩形的四个点映射到Rpoint中
                        for (size_t i = 0; i < 4; i++)  //画出中间R的外接轮廓
                        {
                            line(src, Rpoint[i], Rpoint[(i + 1) % 4], Scalar(0, 255, 255), 2, 8, 0); 
                        }
                        Point2f center = Point2f(centerR.center.x, centerR.center.y);
                        target_predict(src, target, center);
                    }
                }
                
            } //只含有一个内嵌轮廓的父轮廓，在其中进行目标板的筛选及标定，还有目标板出现后中心R的筛选及标定
        }

        imshow("效果视频", src);
        if (_is_recorded)
        {
            writer << src;
        }

        /*连续播放模式与逐帧播放模式判断*/
        if (_is_continuous)
        {
            if (waitKey(30) == ' ')
            {
                if (waitKey(0) == 27)
                {
                    break;
                }
            }
        }
        else
        {
            if (waitKey(0) == 27) //等待键事件以逐帧播放（按esc退出）
            {
                break;
            }
        }

    } //while循环
    capture.release();
    writer.release();

    return 0;
}

